/**
 * Created by xinxinran on 2017/8/23.
 */
'use strict';
const url = require('url');

class Router{
    constructor(){
        this.reqCache = {};
        this.routes = {};
        this.routesTree = {};
    }

    static compileURL(options) {
        if (options.url instanceof RegExp)
            return (options.url);

        let params = [], staticParts = [], pattern = '^', re, _url = url.parse(options.url).pathname;
        _url.split('/').forEach(function (frag) {
            if (frag.length <= 0)
                return (false);

            pattern += '\\/+';
            if (frag.charAt(0) === ':') {
                let label = frag, index = frag.indexOf('('), subexp;
                if (index === -1) {
                    if (options.urlParamPattern) {
                        subexp = options.urlParamPattern;
                    } else {
                        subexp = '[^/]*';
                    }
                } else {
                    label = frag.substring(0, index);
                    subexp = frag.substring(index + 1, frag.length - 1);
                }
                pattern += '(' + subexp + ')';
                params.push(label.slice(1));
            } else {
                staticParts.push(frag);
                pattern += frag;
            }
            return (true);
        });

        if (pattern === '^')
            pattern += '\\/';
        pattern += '$';

        re = new RegExp(pattern, options.flags);
        re.socketParams = params;
        re.staticParts = staticParts;

        return (re);
    }

    registerUrl(method, urlPattern, handler){
        if(!urlPattern || typeof urlPattern !== 'string'){
            throw '[socket router]need string path';
        }
        if(!handler || typeof handler !== 'function'){
            throw '[socket router]need function handler';
        }
        let subTree = this.routesTree[method], re = Router.compileURL({url : urlPattern}), sParts = re.staticParts,
            route, i, len = sParts.length, nowTreeBranch, branchRou;
        if(!subTree){
            subTree = this.routesTree[method] = {};
        }

        route = {
            regexp : re,
            staticParts : sParts,
            paramParts : re.socketParams,
            paramCount : re.socketParams.length,
            name : method + '-' + sParts.join('-') + re.socketParams.length,
            handler : handler
        };

        if(this.routes[route.name]){
            throw '[socket router]duplicate urlPattern : ' + urlPattern;
        }

        nowTreeBranch = subTree;
        for(i = 0; i < len; i++){
            if(!nowTreeBranch[sParts[i]]){
                nowTreeBranch = nowTreeBranch[sParts[i]] = {};
            }else{
                nowTreeBranch = nowTreeBranch[sParts[i]];
            }
        }

        branchRou = nowTreeBranch['#'] = nowTreeBranch['#'] || [];
        branchRou.push(route);
        this.routes[route.name] = route;
    }

    getRoute(path, method){
        let pathParts, nowBranch = this.routesTree[method], routes, nowRoute, cacheKey = method + '|' +path, tmpPath;
        if(nowRoute = this.reqCache[cacheKey]){
            return nowRoute;
        }

        if(!nowBranch){
            return null;
        }
        if(path.charAt(0) === '/'){
            tmpPath = path.substring(1);
        }
        pathParts = tmpPath.split('/');

        pathParts.forEach(part => {
            if(nowBranch[part]){
                nowBranch = nowBranch[part];
            }else{
                return false;
            }
        });

        routes = nowBranch['#'];
        if(!routes){
            return null;
        }

        routes.forEach(r => {
            let info = r.regexp.exec(path), rp, i = 1;
            if(info){
                rp = {};
                nowRoute = {route : r, params: rp};
                r.paramParts.forEach(paramName => {
                    rp[paramName] = info[i++];
                });
                return false;
            }
        });

        if(!nowRoute){
            return null;
        }
        this.reqCache[cacheKey] = nowRoute;

        return nowRoute;
    }
}

module.exports = Router;